package emulator.unittest.analyzer;


import static org.junit.Assert.*;

import java.util.HashMap;

import org.junit.Before;
import org.junit.Test;

import emulator.EmulatorException;
import emulator.analyzer.AddressDefinition;
import emulator.analyzer.CpuExecutionAnalyzer;
import emulator.hardware.HwWord;
import emulator.hardware.memory.MemoryBlock;
import emulator.hardware.nmos6502.Cpu6502;
import emulator.hardware.video.test.NullClock;
import emulator.util.AddressRange;
import emulator.util.MemoryHelper;

public class CpuExecutionAnalyzerTest {
	final static int CODE_SIZE = 0x0010;
	final static int MEM_SIZE = 0x0020;
	private MemoryBlock memory = new MemoryBlock(MEM_SIZE);
	private NullClock clock = new NullClock();
	private Cpu6502 cpu = new Cpu6502();
	private CpuExecutionAnalyzer analyzer;
	
	@Before
	public void setUp() throws Exception {
		AddressRange code_range = new AddressRange(0, CODE_SIZE);
		analyzer = new CpuExecutionAnalyzer(memory, code_range, null);
		cpu.attach(memory, clock);
	}
	
	@Test
	public void testAnalyzeIndirectJump() throws EmulatorException
	{
		MemoryHelper.putData(memory, 0, new byte[] { 
				0x23, 0x01, 		// Address for indirect jump
				0x6C, 0x00, 0x00	// JMP ($0000) -> $0123 
				});
		cpu.setPC(2); analyzer.notifyPreExecute(cpu);
		checkDefinition(analyzer.getAddressMap(), 0, AddressDefinition.TYPE_LOW, 0x0123, "Referenced address (low)");
		checkDefinition(analyzer.getAddressMap(), 1, AddressDefinition.TYPE_HIGH, 0x0123, "Referenced address (high)");
		checkDefinition(analyzer.getAddressMap(), 3, AddressDefinition.TYPE_ADR, 0x0000, "Operand address");
		assertEquals(3, analyzer.getAddressMap().size());
	}
	
	@Test
	public void testAnalyzeDirectJump() throws EmulatorException
	{
		MemoryHelper.putData(memory, 0, new byte[] { 
				0x4C, 0x04, 0x00	// JMP $0004 
				});
		cpu.setPC(0); analyzer.notifyPreExecute(cpu);
		checkDefinition(analyzer.getAddressMap(), 1, AddressDefinition.TYPE_ADR, 0x0004, "Operand address");
		assertEquals(1, analyzer.getAddressMap().size());
	}
	
	@Test
	public void testAnalyzeDirectJumpModified() throws EmulatorException
	{
		MemoryHelper.putData(memory, 0, new byte[] {
				(byte)0xA9, 0x04,		// LDA #4
				(byte)0x8D, 0x06, 0x00,	// STA 6
				0x4C, 0x00, 0x00		// JMP $0000 (modified to $0004) 
				});
		cpu.setPC(0); analyzer.notifyPreExecute(cpu);
		cpu.next(); analyzer.notifyPreExecute(cpu);
		cpu.next(); analyzer.notifyPreExecute(cpu);
		checkDefinition(analyzer.getAddressMap(), 3, AddressDefinition.TYPE_ADR, 0x0006, "Modified location");
		checkDefinition(analyzer.getAddressMap(), 6, AddressDefinition.TYPE_LOW, 0x0004, "Operand address (low)");
		checkDefinition(analyzer.getAddressMap(), 7, AddressDefinition.TYPE_HIGH, 0x0004, "Operand address (high)");
		assertEquals(3, analyzer.getAddressMap().size());
	}
	
	@Test
	public void testAnalyzeIndirectJumpModified() throws EmulatorException
	{
		MemoryHelper.putData(memory, 0, new byte[] {
				0x23, 0x01, 			// Address for indirect jump
				(byte)0xA9, 0x04,		// LDA #4
				(byte)0x8D, 0x00, 0x00,	// STA 0
				0x6C, 0x00, 0x00		// JMP ($0000) ($0123, modified to $0104) 
				});
		cpu.setPC(2); analyzer.notifyPreExecute(cpu);
		cpu.next(); analyzer.notifyPreExecute(cpu);
		cpu.next(); analyzer.notifyPreExecute(cpu);
		analyzer.dumpStats(System.out, "");
		checkDefinition(analyzer.getAddressMap(), 5, AddressDefinition.TYPE_ADR, 0x0000, "Modified location");
		checkDefinition(analyzer.getAddressMap(), 0, AddressDefinition.TYPE_LOW, 0x0104, "Referenced address (low)");
		checkDefinition(analyzer.getAddressMap(), 1, AddressDefinition.TYPE_HIGH, 0x0104, "Referenced address (high)");
		checkDefinition(analyzer.getAddressMap(), 8, AddressDefinition.TYPE_ADR, 0x0000, "Operand address");
		assertEquals(4, analyzer.getAddressMap().size());
	}

	private void checkDefinition(HashMap<Integer, AddressDefinition> address_map, int location,
									int type, int target_address, String description) {
		assertTrue(description+" missing",  address_map.containsKey(location));
		assertEquals(description+" has type "+address_map.get(location).getTypeName(), type, address_map.get(location).getType());
		assertEquals(description+" has wrong target: "+new HwWord(address_map.get(location).getFullAddress()), target_address, address_map.get(location).getFullAddress());
	}
}
